/*
    * Author:   Sander
    * Date:     12-apr-2011
    * Name:     Triangle
    * Version:  1.0
    *
    * This code is property of Sander blahblahblah copyright blahblah.
*/


package javaraytracer.Objects;

import javaraytracer.IntersectionInfo;
import javaraytracer.Materials.Material;
import javaraytracer.Ray;
import javaraytracer.Utils;
import javaraytracer.Vec3;

//PENDING:add function/property description
public class Triangle extends Traceable
{

	private Vec3[] triangle = new Vec3[3];
	private Vec3[] normals = new Vec3[3];
	private double EPSILON = 0.000001;
	private Vec3 edge1, edge2;
	public int smoothingGroup = -1;

	public Triangle(Vec3 p1, Vec3 p2, Vec3 p3)
	{
		defineTriangle(p1, p2, p3, new Material(new Vec3(0.0f, 1.0f, 0.0f), Vec3.EMPTY, 0.0f, 0.0f));
	}
	public Triangle(Vec3 p1, Vec3 p2, Vec3 p3, Material m)
	{
		defineTriangle(p1, p2, p3, m);
	}
	public Triangle(Vec3 p1, Vec3 p2, Vec3 p3, Vec3 n1, Vec3 n2, Vec3 n3, Material m)
	{
		defineTriangle(p1, p2, p3, m);
		normals[0] = n1;
		normals[1] = n2;
		normals[2] = n3;
	}

	private void defineTriangle(Vec3 p1, Vec3 p2, Vec3 p3, Material m)
	{
		triangle[0] = p1;
		triangle[1] = p2;
		triangle[2] = p3;
		
		edge1 = triangle[1].sub(triangle[0]);
		edge2 = triangle[2].sub(triangle[0]);

		normals[0] = edge1.cross(edge2);
		normals[0].normalize();

		material = m;
	}

	@Override
	public IntersectionInfo intersect(Ray r)
	{
		Vec3 pvec, qvec, tvec, dir;
		double det, inv_det, u, v, t;
		
		dir = r.direction.clone();
		
		// begin calculating determinant - also used to calculate U parameter
		pvec = dir.cross(edge2);

		// if determinant is near zero- ray lies in plane of triangle
		det = pvec.dot(edge1);


		if ( (det > -EPSILON) && (det < EPSILON) )
		{
			return new IntersectionInfo(false);
		}

		inv_det = (1.0 / det);

		/* calculate distance from vert0 to ray origin */
		tvec = r.origin.sub(triangle[0]);

		/* calculate U parameter and test bounds */
		u = (inv_det * tvec.dot(pvec));
		if ( (u < 0.0) || (u > 1.0) )
		{
			return new IntersectionInfo(false);
		}

		/* prepare to test V parameter */
		qvec = tvec.cross(edge1);

		/* calculate V parameter and test bounds */
		v = (inv_det * qvec.dot(dir));
		if ( (v < 0.0) || ((u + v) > 1.0) )
		{
			return new IntersectionInfo(false);
		}

		/* calculate t, ray intersecs triangle */
		t = (qvec.dot(edge2) * inv_det);
		
		
		Vec3 loc = r.origin.add(dir.mul(t));
		
		//interpolation
		Vec3 normal;
		if(normals[1] != null || normals[2] != null)
		{
			normal = Utils.interpolate(normals[0], normals[1], normals[2], u, v);
		}
		else
		{
			normal = normals[0];
		}
		
		//Bumpmap
		if(material.bumpMap != null)
		{
			Vec3 pos = Utils.interpolate(material.offset[0], material.offset[1], material.offset[2], u, v);
			int x = (int)(pos.x * (material.bumpMap.imageWidth - 1));
			int y = (int)(pos.y * (material.bumpMap.imageHeight - 1));
			int data = material.bumpMap.data[y][x];
			double R = ((data >> 16) & 0xFF) / 255.0;
			double G = ((data >> 8) & 0xFF) / 255.0;
			double B = (data & 0xFF) / 255.0;
			R = (R - 0.5) * 2;
			G = (G - 0.5) * 2;
			B = (B - 0.5) * 2;
			
			Vec3 bump = new Vec3(R, G, B);
			Vec3 half = normal.add(new Vec3(0.0 ,0.0 ,1.0)).normalized();
			bump.x = - bump.x;
			bump.y = - bump.y;
			//bump mirror by half: 2 * (bump . half) * half - bump
			normal = half.mul(bump.dot(half) * 2.0).sub(bump);
		}
		
		//Vec3 normal = (u > 0.5 ? normals[1] : (v > 0.5 ? normals[2] : normals[0])); //only one, creates triangles
		//Vec3 normal = normals[0].add(normals[1]).add(normals[2]).normalized(); //average normal

		IntersectionInfo ii = new IntersectionInfo(loc, normal, (loc.sub(r.origin)).length(), this);
		ii.u = u;
		ii.v = v;
		return ii;
	}


	@Override
	public boolean hit(Ray r)
	{
		
		Vec3 pvec, qvec, tvec, dir;
		double det, inv_det, u, v, t;
		// find vectors for two edges sharing vert
		
		dir = r.direction;

		// begin calculating determinant - also used to calculate U parameter
		pvec = dir.cross(edge2);

		// if determinant is near zero- ray lies in plane of triangle
		det = pvec.dot(edge1);


		if ( (det > -EPSILON) && (det < EPSILON) )
		{
			return false;
		}

		inv_det = (1.0 / det);

		/* calculate distance from vert0 to ray origin */
		tvec = r.origin.sub(triangle[0]);

		/* calculate U parameter and test bounds */
		u = (inv_det * tvec.dot(pvec));
		if ( (u < 0.0) || (u > 1.0) )
		{
			return false;
		}

		/* prepare to test V parameter */
		qvec = tvec.cross(edge1);

		/* calculate V parameter and test bounds */
		v = (inv_det * qvec.dot(dir));
		if ( (v < 0.0) || ((u + v) > 1.0) )
		{
			return false;
		}
		/* calculate t, ray intersecs triangle */
		t = (qvec.dot(edge2) * inv_det);
		if(t < 0.0 || t > 1.0)
		{
			return false;
		}

		return true;
	}
	
	public Vec3 getVector(int i)
	{
		return triangle[i];
	}


	@Override
	public Material getMaterial()
	{
		return material;
	}


	@Override
	public Material getMaterial(IntersectionInfo info)
	{
		material.position(info);
		return material;
	}

}
